import { fabric } from 'fabric';

import { cropPathId } from '@/pages/TemplateEditor/components/Editor/components/TemplateEffect/components/Screen/constant';

const lineColor = 'rgba(238, 77, 45, 0.5)';
const lineWidth = 2;
const lineMargin = 5;
const aligningLineOffset = 50;
const lineWidthBy2 = lineWidth / 2;

declare module 'fabric' {
  namespace fabric {
    interface Canvas {
      contextTop?: CanvasRenderingContext2D;
    }
  }
}

interface Bounding {
  width: number;
  height: number;
  centerX: number;
  centerY: number;
  left: number;
  right: number;
  top: number;
  bottom: number;
}

export function initAligningGuidelines(canvas: fabric.Canvas): void {
  const ctx = canvas.getSelectionContext();
  let viewportTransform;
  let zoom = 1;
  const canvasWidth = canvas.getWidth();
  const canvasHeight = canvas.getHeight();
  let canvasBounding: Bounding;

  function drawVerticalLine(coords): void {
    if (!coords) {
      return;
    }

    const x = Math.min(
      Math.max(coords.x, canvasBounding.left + lineWidthBy2),
      canvasBounding.right - lineWidthBy2
    );

    drawLine(x, coords.y1, x, coords.y2);
  }

  function drawHorizontalLine(coords): void {
    if (!coords) {
      return;
    }

    const y = Math.min(
      Math.max(coords.y, canvasBounding.top + lineWidthBy2),
      canvasBounding.bottom - lineWidthBy2
    );

    drawLine(coords.x1, y, coords.x2, y);
  }

  /**
   *
   * 在电脑图形学里面，视口代表了一个可看见的多边形区域（通常来说是矩形）。
   * 在浏览器范畴里，它代表的是浏览器中网站可见内容的部分。视口外的内容在被滚动进来前都是不可见的。
   * 视口当前可见的部分叫做可视视口（visual viewport）。
   * 可视视口可能会比布局视口（layout viewport）更小，因为当用户缩小浏览器缩放比例时，布局视口不变，而可视视口变小了。
   *
   * viewportTransform 是一个数组，里面有6个元素，默认值是 [1, 0, 0, 1, 0, 0]。
   * 从下标0开始，它们分别代表：
   * [0]: 水平缩放（x轴方向）
   * [1]: 水平倾斜（x轴方向）
   * [2]: 垂直倾斜（y轴方向）
   * [3]: 垂直缩放（y轴方向）
   * [4]: 水平移动（x轴方向）
   * [5]: 垂直移动（y轴方向）
   *
   * zoom: canvas面板缩放系数，下面在面板上画线的时候需要考虑当前的缩放系数
   *
   */
  function drawLine(x1, y1, x2, y2): void {
    // 两个点决定一条线
    ctx.save();
    ctx.lineWidth = lineWidth; // 宽度
    ctx.strokeStyle = lineColor; // 颜色
    ctx.beginPath();
    ctx.setLineDash([3, 3]); // 虚线
    ctx.moveTo(
      (x1 + viewportTransform[4]) * zoom,
      (y1 + viewportTransform[5]) * zoom
    );
    ctx.lineTo(
      (x2 + viewportTransform[4]) * zoom,
      (y2 + viewportTransform[5]) * zoom
    );
    ctx.stroke();
    ctx.restore();
  }

  function isInRange(value1, value2): boolean {
    value1 = Math.round(value1);
    value2 = Math.round(value2);
    return value2 >= value1 - lineMargin && value2 <= value1 + lineMargin;
  }

  function getBounding(object: fabric.Object): Bounding {
    const { x: centerX, y: centerY } = object.getCenterPoint();

    // getBoundingRect方法里，获取的是包含旋转的上下左右边界，但是未计算zoom的放大缩小
    const activeObjectBoundingRect = object.getBoundingRect();
    const height = activeObjectBoundingRect.height / viewportTransform[3];
    const width = activeObjectBoundingRect.width / viewportTransform[0];

    const left = centerX - width / 2;
    const right = centerX + width / 2;
    const top = centerY - height / 2;
    const bottom = centerY + height / 2;

    return { width, height, centerX, centerY, left, right, top, bottom };
  }

  let verticalLine = null;
  let horizontalLine = null;

  const xKeys = ['left', 'centerX', 'right'];
  const yKeys = ['top', 'centerY', 'bottom'];

  canvas.on('mouse:down', function () {
    viewportTransform = canvas.viewportTransform;
    zoom = canvas.getZoom();
    const canvasBoundingWidth = canvasWidth / zoom;
    const canvasBoundingHeight = canvasHeight / zoom;
    canvasBounding = {
      width: canvasBoundingWidth,
      height: canvasBoundingHeight,
      centerX: canvasBoundingWidth / 2,
      centerY: canvasBoundingHeight / 2,
      left: 0,
      right: canvasBoundingWidth,
      top: 0,
      bottom: canvasBoundingHeight
    };
  });

  // eslint-disable-next-line cognitive-complexity/cognitive-complexity-forced
  canvas.on('object:moving', function (e) {
    // object移动的时候开始计算是否要显示辅助线

    if (e.target && e.target.data?.id === cropPathId) {
      // 裁剪时不显示辅助线
      return;
    }

    const activeObject = e.target;
    const canvasObjects = canvas.getObjects();
    const activeObjectBounding = getBounding(activeObject); // 获取当前拖动的obj的主要信息（长宽、位置等，见 interface Bounding）

    let newCenterX = activeObjectBounding.centerX;
    let newCenterY = activeObjectBounding.centerY;

    // 对于当前这个在拖动中的object，有哪些元素是需要进行对比，决定是否要展示辅助线的
    const objectBoundingPosArr = [
      canvasBounding, // 整个canvas画布
      ...canvasObjects // 当前界面上其他object
        .filter((object) => object !== activeObject)
        .map(getBounding)
    ];

    // 清空之前的辅助线数据
    verticalLine = horizontalLine = null;

    // 循环每一个可能要展示辅助线的元素，看看有哪个是匹配上的（位置接近
    objectBoundingPosArr.forEach((objectBounding) => {
      setVerticalLine(objectBounding);
      setHorizontalLine(objectBounding);
    });

    function setVerticalLine(objectBounding: Bounding): void {
      if (verticalLine) {
        return;
      }

      // 垂直基准线的上下两端
      // 这里的aligningLineOffset是辅助线超出的长度（固定值）
      // top取两者最小（最靠上），bottom取两者最大（最靠下）
      const y1 =
        Math.min(objectBounding.top, activeObjectBounding.top) -
        aligningLineOffset;
      const y2 =
        Math.max(objectBounding.bottom, activeObjectBounding.bottom) +
        aligningLineOffset;

      // 画垂直线需要使用x坐标来比较
      // 两个元素的（某一条边/中心）横向坐标接近，才会显示垂直辅助线
      // const xKeys = ['left', 'centerX', 'right'];
      xKeys.some((activeObjectKey) => {
        // 当前拖动的obj的 left/centerX/right 值
        const activeObjectX = activeObjectBounding[activeObjectKey];
        // 比较的 left/centerX/right 距离中心点的差值，用于计算新的中心点
        const diffCenterX = activeObjectBounding.centerX - activeObjectX;

        // const xKeys = ['left', 'centerX', 'right'];
        return xKeys.some((objectKey) => {
          // 另一个要比较的元素的 left/centerX/right 值
          const objectX = objectBounding[objectKey];

          // isInRange：比较差值是否小于等于5
          if (isInRange(objectX, activeObjectX)) {
            // x值固定，y点有两个值
            verticalLine = { x: objectX, y1, y2 };
            // 新的中心点x坐标为：新的left/centerX/right 加上 与中心点原来的差值
            newCenterX = objectX + diffCenterX;
            return true;
          }

          return false;
        });
      });
    }

    // 水平辅助线逻辑同上
    function setHorizontalLine(objectBounding: Bounding): void {
      if (horizontalLine) {
        return;
      }

      // 水平基准线的左右两端
      const x1 =
        Math.min(objectBounding.left, activeObjectBounding.left) -
        aligningLineOffset;
      const x2 =
        Math.max(objectBounding.right, activeObjectBounding.right) +
        aligningLineOffset;

      // 画水平线需要使用y坐标来比较
      yKeys.forEach((activeObjectKey) => {
        const activeObjectY = activeObjectBounding[activeObjectKey];
        // 比较的位置距离中心点的差值
        const diffCenterY = activeObjectBounding.centerY - activeObjectY;

        yKeys.forEach((objectKey) => {
          const objectY = objectBounding[objectKey];

          if (isInRange(objectY, activeObjectY)) {
            horizontalLine = { y: objectY, x1, x2 };
            // 新的中心点y坐标为比较位置加上差值
            newCenterY = objectY + diffCenterY;
          }
        });
      });
    }

    // 如果有辅助线匹配上了，就把当前在拖动中的object的位置改过去，以达到吸附效果
    if (verticalLine || horizontalLine) {
      activeObject.setPositionByOrigin(
        new fabric.Point(newCenterX, newCenterY),
        'center',
        'center'
      );
    }
  });

  // 隐藏辅助线：渲染开始之前（只要object移动，都会重新渲染）
  canvas.on('before:render', function () {
    if (canvas.contextTop) {
      canvas.clearContext(canvas.contextTop);
    }
  });

  // 画辅助线：渲染完成之后，此时object:moving事件已经触发，辅助线位置已经计算好，object也移动到位
  canvas.on('after:render', function () {
    drawVerticalLine(verticalLine);
    drawHorizontalLine(horizontalLine);
  });

  // 隐藏辅助线：鼠标抬起
  canvas.on('mouse:up', function () {
    verticalLine = horizontalLine = null;
    if (canvas.contextTop) {
      canvas.clearContext(canvas.contextTop);
    }
    canvas.renderAll();
  });
}
